package ru.saveidea.bytearray {
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.utils.ByteArray;

	/**

	 * @author ign
	 */
	public class PATDecoder {

		private static const IMAGE_MODE_RGB : String = 'IMAGE_MODE_RGB';
		private static const IMAGE_MODE_GRAYSCALE : String = 'IMAGE_MODE_GRAYSCALE';

		private static var pattern_start : Number;
		private static var next_pattern : Number;

		public static function decode(data : ByteArray) : Array {
			return pspat_load(data);
		}

		private static function pspat_load(data : ByteArray) : Array {
			var bitmaps : Array = [];	
			if (data.readUTFBytes(4) != '8BPT') {
				throw new Error('Not a photoshop pattern file\n');
			}
			var version : Number = data.readUnsignedShort();
			if (version != 1) {
				throw new Error('ERROR: unable to decode pat format version');
			}
			var pat_count : Number = read_ulong(data);
			if (pat_count == 0) {
				throw new Error('ERROR: no patterns found');
			}
			
			for (var i : Number = 0;i < pat_count;i++) {
				bitmaps.push(pspat_sample_load(data));
			}
			
			return bitmaps;
		}

		private static function pspat_sample_load(data : ByteArray) : Bitmap {
			
			//version
			read_ulong(data);
			var image_type : Number = read_ulong(data);
			
			//swidth
			data.readUnsignedShort();			
			//sheight
			data.readUnsignedShort();
			var name : String = read_name(data);
			
			//id
			data.readUTFBytes(37);
			
			var color_model : Number = read_ulong(data);
			var image_mode : String;
			
			var nb_channels : Number;
			switch (image_type) {
				case 3: 
					image_mode = IMAGE_MODE_RGB;
					/* RGB */
					nb_channels = 3;
					break;
				case 1: 
					/* GRAY */
					image_mode = PATDecoder.IMAGE_MODE_GRAYSCALE;
					nb_channels = 1;
					break;
				default:
					throw new Error('Unsupported color model' + ' ' + color_model);
					break;
			}
 
			
			var pattern_size : Number = read_ulong(data);
			pattern_start = data.position;
			next_pattern = pattern_size + pattern_start;
			
			var top : Number = read_ulong(data);
			var left : Number = read_ulong(data);
			var bottom : Number = read_ulong(data);
			var right : Number = read_ulong(data);
			//depth;
			read_ulong(data);
			var w : Number = bottom - top;
			var h : Number = right - left;
			//var size : Number = w * 3 * h;
			var channels : Array = [];
			
			for (var i : Number = 0;i < nb_channels;i++) {
				var channel : ByteArray = new ByteArray();
				channels.push(channel);
				pat_sample_parse(data, channel);
			}
			
			//собрать битмапу
			var bitmap : Bitmap = makeBitmap(getColors(channels, image_mode), w, h);
			bitmap.name = name;
			/*
			if (data.position < next_pattern - (88 + 31)) {
			//trace('< да');
			data.position += 88;
			}
			 * 
			 */
			data.position = pattern_start + pattern_size;
			return bitmap;
		}

		private static function getColors(channels : Array, image_mode : String) : Array {
			var r : ByteArray = channels[0];
			var len : Number = r.bytesAvailable;
			var colors : Array = [];
			var i : Number = 0;
			if (image_mode == IMAGE_MODE_RGB) {
				var g : ByteArray = channels[1];
				var b : ByteArray = channels[2];
				for (i = 0;i < len; i++) {
					colors.push(getColor(r.readByte(), g.readByte(), b.readByte()));
				}
			} else if (image_mode == IMAGE_MODE_GRAYSCALE) {
				for (i = 0;i < len; i++) {
					var pixelColor : Number = r.readByte();
					colors.push(getColor(pixelColor, pixelColor, pixelColor));
				}
			}
			
			return colors;
		}

		private static function makeBitmap(colors : Array, w : Number,h : Number) : Bitmap {
			var bd : BitmapData = new BitmapData(w, h, false, 0);
			var total : Number = w * h;
			var xpos : Number = 0;
			var ypos : Number = 0;
			for (var i : Number = 0;i < total; i++) {
				bd.setPixel(xpos, ypos, colors[i]);
				xpos++;
				if (xpos == w) {
					ypos++;
					xpos = 0;
				}
			}
			return new Bitmap(bd);
		}

		private static function getColor(r : Number,g : Number,b : Number) : uint {
			var arr : Array = [r,g,b];
			var val : Number;
			
			for (var i : Number = 0;i < arr.length; i++) {
				val = arr[i];
				if (val < 0) {					
					val += 256;
				}
				arr[i] = val;
			}
			return arr[0] << 16 | arr[1] << 8 | arr[2];
		}

		private static function pat_sample_parse(data : ByteArray,chanelData : ByteArray) : void {
			//version
			read_ulong(data);
			//sample_size
			read_ulong(data);
			//depth_unused
			read_ulong(data);			
			var top : Number = read_ulong(data);
			var left : Number = read_ulong(data);
			var bottom : Number = read_ulong(data);
			var right : Number = read_ulong(data);
			var depth : Number = data.readUnsignedShort();
			var compression : Number = data.readUnsignedByte();
			
			//trace('!',version, sample_size, depth_unused,top,left,bottom,right,depth,compression);
			var w : Number = bottom - top;
			var h : Number = right - left;
			var size : Number = w * (depth >> 3) * h;

			if (compression != 0) {
				rle_decode(data, chanelData, h * (depth >> 3));
			} else {
				data.readBytes(chanelData, 0, size);
			}
		}

		private static function read_name(data : ByteArray) : String {
			var name_size : Number = read_ulong(data);
			var buff_size : Number = name_size * 2;
			var name : String = '';
			for (var i : Number = 0;i < buff_size; i += 1) {
				name += data.readUTFBytes(1);
			}
			return name;
		}

		private static function rle_decode(b : ByteArray, result : ByteArray, h : Number) : void {
			var cscanline_len : Array = [];
			var i : Number = 0;
			var c : Number = 0;
			for (i = 0;i < h; i++) {
				cscanline_len[i] = b.readUnsignedShort();
			}
			for (i = 0;i < h; i++) {
				for (var j : Number = 0;j < cscanline_len[i]; j) {
					var n : Number = b.readByte();
					j++;
					if (n >= 128) {
						n -= 256;
					}
					if (n < 0) {
						if (n == -128) {
							continue;
						}
						n = -n + 1;

						var ch : Number = b.readByte();
						j++;
						for (c = 0;c < n; c++) {
							result.writeByte(ch);
						}
					} else {
						for (c = 0;c < n + 1; c++) {
							j++;
							result.writeByte(b.readByte());
						}
					}
				}
			}
			result.position = 0;
		}

		private static function read_ulong(ba : ByteArray) : uint {
			return ba.readUnsignedInt();
		}
	}
}